package com.teatime.client.image.photomgr;

import java.util.ArrayList;
import java.util.List;

import com.teatime.client.Constants;
import com.teatime.client.R;
import com.teatime.client.TLog;
import com.teatime.client.TeaTimeUtils;
import com.teatime.client.image.photomgr.PhotoMgrView.PhotoInfo;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.view.animation.AnimationSet;
import android.view.animation.Animation.AnimationListener;
import android.widget.ImageView;

public class PhotoStackView extends View
		implements AnimationListener {

	private int mPhotoWidth = 50; /*dip*/
	private int mPhotoHeight = 50;
	private int mCoverPhotoWidth = 100;
	private int mCoverPhotoHeight = 100;
	private int mBorderWidth = 2;

	private Paint mOutlinePaint;
	private Paint mImagePaint;
	private List<PhotoMatrixInfo> mPhotos;
	private Bitmap mBorderBmp;
	private Bitmap mInitBmp;
	private ImageView mImgPhotoFlying;
	private boolean mFlyingAnimation;
	private boolean mIsFlying;
	private int     mTransMode = 1;
	
	Matrix mMatrix;

	private int dip2px(int dip) {
		return TeaTimeUtils.dip2px(getContext(), dip);
	}
	
	public PhotoStackView(Context context, AttributeSet attrs) {
		super(context, attrs);
		
		mPhotoWidth 	  = dip2px(mPhotoWidth);
		mPhotoHeight 	  = dip2px(mPhotoHeight);
		mCoverPhotoWidth  = dip2px(mCoverPhotoWidth);
		mCoverPhotoHeight = dip2px(mCoverPhotoHeight);
		mBorderWidth 	  = dip2px(mBorderWidth);
		
		initAttrs(attrs);
		
		mMatrix = new Matrix();
		//setScaleType(ScaleType.CENTER_INSIDE);
		
		mPhotos = new ArrayList<PhotoMatrixInfo>();
		mImagePaint = new Paint();
		mImagePaint.setAntiAlias(true);
		mOutlinePaint = new Paint();
		mOutlinePaint.setStrokeWidth(1F);
		mOutlinePaint.setStyle(Paint.Style.STROKE);
		mOutlinePaint.setColor(Color.BLACK);
		
		//mBorderBmp = BitmapFactory.decodeResource(getResources(), R.drawable.white_bg);
		mBorderBmp = Bitmap.createBitmap(mPhotoWidth + 10, mPhotoHeight + 10, Config.ARGB_8888);
		mBorderBmp.eraseColor(Color.WHITE);
		mImgPhotoFlying = null;
		mFlyingAnimation = true;
		mIsFlying = false;
	}

	protected void initAttrs(AttributeSet attrs) {
		TypedArray a = getContext().obtainStyledAttributes(attrs,
                R.styleable.teatime);
		if (a == null) {
			return;
		}
		
		for (int i = 0; i < a.getIndexCount(); i ++) {
            int attr = a.getIndex(i);
            switch (attr) {
            case R.styleable.teatime_photoWidth:
            	mPhotoWidth = (int)a.getDimension(attr, mPhotoWidth);
                break;
            case R.styleable.teatime_photoHeight:
            	mPhotoHeight = (int)a.getDimension(attr, mPhotoHeight);
                break;
            case R.styleable.teatime_photoBorderWidth:
            	mBorderWidth = (int)a.getDimension(attr, 2);
            	break;
            case R.styleable.teatime_coverPhotoWidth:
            	mCoverPhotoWidth = (int)a.getDimension(attr, mCoverPhotoWidth);
                break;
            case R.styleable.teatime_coverPhotoHeight:
            	mCoverPhotoHeight = (int)a.getDimension(attr, mCoverPhotoHeight);
                break;
            case R.styleable.teatime_initPhotoSrc:
            	int bmpId = a.getResourceId(attr, -1);
            	if (bmpId > 0) {
            		mInitBmp = BitmapFactory.decodeResource(getResources(), bmpId);
            	}
            	else {
            		mInitBmp = null;
            	}
            	break;
            case R.styleable.teatime_showAnimation:
            	mFlyingAnimation =  a.getBoolean(attr, true);
            }
        }
        a.recycle();
	}
	
	@Override
	protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
		int width, height;
		if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY 
		    && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ) {
			width = MeasureSpec.getSize(widthMeasureSpec);
			height = (int)((float)width / Constants.ImageWHRation);
			setMeasuredDimension(width, height);
		}
		else {
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		}
		
	}
	
	private ImageView createFlyingImageView() {
		ViewGroup.LayoutParams windowParams = 
				new ViewGroup.LayoutParams(
						dip2px(Constants.SmallImageWidth), 
						dip2px(Constants.SmallImageHeight));

        ImageView iv = new ImageView(getContext());
        iv.setVisibility(GONE);
        iv.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        ((Activity)getContext()).addContentView(iv, windowParams);
        return iv;
	}
	
	private AnimationSet buildFlyingAnimation(PhotoMatrixInfo pi) {
		Animation ra = new RotateAnimation(0, 360 + pi.degree, 
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
		ra.setDuration(1000);
		
		Animation sa = new ScaleAnimation(
				1.2f, pi.scale, 1.2f, pi.scale, 
                Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
		sa.setDuration(1000);

		int p[] = new int[2];
		this.getLocationInWindow(p);
		
		Animation ta = new TranslateAnimation(Animation.RELATIVE_TO_PARENT,
                1.0f, Animation.ABSOLUTE, p[0] + pi.tx, 
                Animation.RELATIVE_TO_PARENT, 0.4f, 
                Animation.ABSOLUTE, p[1] + pi.ty - TeaTimeUtils.getWindowStatusBarHeight(getContext()));
		ta.setDuration(1000);
		
		AnimationSet as = new AnimationSet(getContext(), null);
		as.setDuration(1000);
		as.addAnimation(ra);
		as.addAnimation(sa);
		as.addAnimation(ta);
		as.setAnimationListener(this);
		
		return as;
	}
	
	private boolean startFlyingAnim(PhotoMatrixInfo pi) {
		
		if (mImgPhotoFlying == null) {
			mImgPhotoFlying = createFlyingImageView();
		}

		if (mImgPhotoFlying != null) {
			if (!mIsFlying) {
				mImgPhotoFlying.setImageBitmap(pi.bitmap);
				//AnimationUtils.loadAnimation(getContext(), R.anim.anim_photo_add);
			
				mIsFlying = true;
				mImgPhotoFlying.startAnimation(buildFlyingAnimation(pi));
			}
			return true;
		}
		else {
			return false;
		}
	}
	
	private int randInRange(int low, int high) {
		double r = Math.random() * ((double)(high - low));
		r += low;
		return (int)(r + 0.5f);
	}
	
	private int randInMirrorRange(int low, int high, int mirrorPos) {
		int r = randInRange(low, high);
		if (randBoolean()) {
			r = 2 * mirrorPos - high;
		}
		
		return r;
	}
	
	private PhotoMatrixInfo getCover() {
		if (mPhotos.size() > 0) {
			return mPhotos.get(0);
		}
		else {
			return null;
		}
	}
	
	private int getCoverPhotoWidth() {
		PhotoMatrixInfo cover = getCover();
		if (cover != null) {
			return cover.draww;
		}
		else {
			return mCoverPhotoWidth;
		}
	}
	
	private int getCoverPhotoHeight() {
		PhotoMatrixInfo cover = getCover();
		if (cover != null) {
			return cover.drawh;
		}
		else {
			return mCoverPhotoHeight;
		}
	}
	
	private int randx(int width, boolean fix) {
		int r, vw = getWidth(), cw = getCoverPhotoWidth();
		if (mTransMode == 1) {
			return randInRange(10, vw - width - 10);
		}
		else {
			int low = (vw - cw) / 2,
				high = (vw - cw) / 2 + cw;
			if (fix) {
				low = high = (vw - cw) / 2;
				r = randInMirrorRange(low, high, vw / 2);
			}
			else {
				r = randInRange(low, high);
				//TLog.v("randx, low:" + low + ",high:" + high + "r:" + r + ",vw:" + vw + ",coverPhotoWidth:" + cw);
			}
			return r - (width / 2);
		}
		
	}
	
	private int randy(int height, boolean fix) {
		int r, vh = getHeight(), ch = getCoverPhotoHeight();
		if (mTransMode == 1) {
			return randInRange(10, vh - height - 10);
		}
		else {
			int low = (vh - ch) / 2,
			high = (vh - ch) / 2 + ch;
			if (fix) {
				low = high = (vh - ch) / 2;
				r = randInMirrorRange(low, high, vh / 2);
			}
			else {
				
				r = randInRange(low, high);
				//TLog.v("randy, low:" + low + ",high:" + high + "r:" + r + ",vh:" + vh + ",coverPhotoHeight:" + ch);
			}
			
			return r - (height / 2);
		}
	}
	
	private boolean randBoolean() {
		return (Math.random() >= 0.5);
	}
	
	private int[] randxy(int width, int height) {
		int x, y;
		if (randBoolean()) {
			x = randx(width, true);
			y = randy(height, false);
		}
		else {
			x = randx(width, false);
			y = randy(height, true);
		}
		
		int [] p = new int[2];
		p[0] = x;
		p[1] = y;
		return p;
	}
	
	private PhotoMatrixInfo calcPhotoInfo(Bitmap bitmap, 
			int dstWidth, int dstHeight,
			boolean crop,
			boolean rotate, 
			boolean randomTrans/* true for random translate,
			 					  false for view center */) {
		float sw = bitmap.getWidth(), sh = bitmap.getHeight();
		float bw = mBorderBmp.getWidth(), bh = mBorderBmp.getHeight();
		
		float scaleX = (float)dstWidth / (float)sw, 
			  scaleY = (float)dstHeight / (float)sh;
		float scale;
		if (crop) {
			scale = Math.max(scaleX, scaleY);
		}
		else {
			scale = Math.min(scaleX, scaleY);
		}
		float bw_scaled = sw * scale + mBorderWidth * 2,
				bh_scaled = sh * scale + mBorderWidth * 2;

		float degree = 0;
		if (rotate) {
			degree = (float)(15.0f * Math.random() + 3.0);
			if (Math.random() >= 0.5f) {
				degree *= -1.0f;
			}
		}
		
		PhotoMatrixInfo pi = new PhotoMatrixInfo();
		
    	pi.bitmap = bitmap;
    	pi.degree = degree;
    	pi.scale = scale;
    	pi.bsw = bw_scaled / bw;
    	pi.bsh = bh_scaled / bh;
    	pi.draww = (int)(sw * scale);
		pi.drawh = (int)(sh * scale);
		
    	if (randomTrans) {
    		int p[] = randxy(pi.draww, pi.drawh);
    		pi.tx = p[0];
    		pi.ty = p[1];
    	}
    	else {
    		pi.tx = (getWidth() - pi.draww) / 2;
    		pi.ty = (getHeight() - pi.drawh) / 2;
    		
    	}
    	
    	return pi;
	}
	
	private PhotoMatrixInfo createPhotoInfo(Bitmap bitmap, boolean isCover) {
		PhotoMatrixInfo pi;
		if (isCover) {
			pi = calcPhotoInfo(bitmap, 
					mCoverPhotoWidth, mCoverPhotoHeight, true, false, false);
		}
		else {
			pi = calcPhotoInfo(bitmap, 
    			mPhotoWidth, mPhotoHeight, false, true, true);
		}
		return pi;
	}

	public void addBitmap(Bitmap bitmap) {
		/*super.setImageDrawable(drawable);*/
		PhotoMatrixInfo pi = createPhotoInfo(bitmap, (mPhotos.size() == 0));
    	
    	mPhotos.add(pi);
    	if(mFlyingAnimation) {
    		if (!startFlyingAnim(pi)) {
    			invalidate();
    		}
		}
    	else {
    		invalidate();
    	}
    	
	}
	
	private void drawPhoto(Canvas canvas, PhotoMatrixInfo pi) {
		Bitmap bmp =  pi.bitmap;
		if (bmp.isRecycled()) {
			return;
		}
		
    	mMatrix.setScale(pi.bsw, pi.bsh);
    	mMatrix.postTranslate(pi.tx - mBorderWidth, 
    			pi.ty - mBorderWidth);
    	if (pi.degree != 0) {
    		mMatrix.postRotate(pi.degree, 
    			(pi.draww) / 2, (pi.drawh) / 2);
    	}
		//TLog.v("rotate: " + degree);
    	
    	canvas.drawBitmap(mBorderBmp, mMatrix, mImagePaint);

    	mMatrix.setScale(pi.scale, pi.scale);
    	mMatrix.postTranslate(pi.tx, pi.ty);
    	if (pi.degree != 0) {
    		mMatrix.postRotate(pi.degree, 
    			(pi.draww) / 2, (pi.drawh) / 2);
    	}
		//TLog.v("rotate: " + degree);
    	
    	//d.draw(canvas);
    	canvas.drawBitmap(bmp, mMatrix, mImagePaint);
	}
	
	@Override  
    protected void onDraw(Canvas canvas) {  
        // TODO Auto-generated method stub
        //super.onDraw(canvas);
        int i;
        if (mPhotos.size() <= 0 && mInitBmp != null) {
        	int bw = mInitBmp.getWidth(),
        		bh = mInitBmp.getHeight(),
        		w = getWidth(),
        		h = getHeight();
        	if (bw <= w && bh <= h) {
        		mMatrix.setTranslate((w - bw) / 2, (h - bh) / 2);
        	}
        	else {
        		float scaleX = (float)w / (float)bw,
        			  scaleY = (float)h / (float)bh;
        		float scale = Math.min(scaleX, scaleY);
        		mMatrix.setScale(scale, scale);
        		mMatrix.postTranslate((w - (float)bw * scale) / 2, 
        				(h - (float)bh * scale) / 2);
        	}
        	canvas.drawBitmap(mInitBmp, mMatrix, mImagePaint);
        }
        
        // start with the second photo,
        // the first one is the cover,
        // draw it as the last one
        for (i = 0; i < mPhotos.size(); i ++) {
        	if (mIsFlying && i == mPhotos.size() - 1) {
        		// if is flying, the last photo is the flying
        		// photo, so skip this one
        		break;
        	}
        	PhotoMatrixInfo pi = mPhotos.get(i);
        	drawPhoto(canvas, pi);
        	
        }
        
        /*if (mPhotos.size() > 0) {
        	if (!mIsFlying || mPhotos.size() > 1) {
        		PhotoMatrixInfo pi = mPhotos.get(0);
        		drawPhoto(canvas, pi);
        	}
        	
        }*/

        canvas.drawRect(0, 0, getWidth() - 1 , getHeight() - 1, mOutlinePaint);
	}
	
	public PhotoMatrixInfo findPhotoInfoByBitmap(Bitmap bitmap) {
		PhotoMatrixInfo pi;
		int i;
		for (i = 0; i < mPhotos.size(); i ++) {
			pi = mPhotos.get(i);
			if (pi.bitmap == bitmap) {
				return pi;
			}
		}
		return null;
	}
	
	public void updatePhotos(List<PhotoInfo> photos) {
		List<PhotoMatrixInfo> newPhotos = new ArrayList<PhotoMatrixInfo>(), 
				oldPhotos;
		for (int i = 0; i < photos.size(); i ++) {
			PhotoInfo pi = photos.get(i);
			if (pi != null && pi.bitmap != null) {
				TLog.v("updatePhotos: i=" + i);
				PhotoMatrixInfo pmi = createPhotoInfo(pi.bitmap, (newPhotos.size() == 0));
				newPhotos.add(pmi);
			}
		}
		
		oldPhotos = mPhotos;
		mPhotos = newPhotos;
		oldPhotos.clear();
		oldPhotos = null;
		invalidate();
	}
	
	@Override
	public void onAnimationEnd(Animation a) {
		// TODO Auto-generated method stub
		//TLog.v("photostack: flying image onAnimationEnd");
		if (mImgPhotoFlying != null) {
			mImgPhotoFlying.setVisibility(View.GONE);
		}
		mIsFlying = false;
		
	}

	@Override
	public void onAnimationRepeat(Animation a) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void onAnimationStart(Animation a) {
		//TLog.v("photostack: flying image onAnimationStart");
		if (mImgPhotoFlying != null) {
			mImgPhotoFlying.setVisibility(View.VISIBLE);
		}
		mIsFlying = true;
		
	}
	
	static class PhotoMatrixInfo {
		public Bitmap bitmap;
		public float degree;
		public float scale;
		public float bsw;
		public float bsh;
		public int tx;
		public int ty;
		public int draww;
		public int drawh;
	}
}
